# Учебный проект: Иконка корзины

"Корзина" - компонент интерфейса онлайн магазина или ресторана, в который пользователи добавляют товары для заказа.

В нашем проекте она состоит из двух частей, первая - это компонент "Иконка корзины". Она появляется на странице, когда пользователь добавляет хотя бы один товар в корзину, и исчезает, когда все товары удаляются.

В этой задаче у вас уже есть файл `index.js`, в котором находится класс `CartIcon`, описывающий этот компонент.

Сейчас в нём сделана только "отрисовка" корзины, нужно будет добавить её позиционирование при прокрутке, чтобы иконка корзины всегда была видима.

Исходный файл `index.js`:

```js
import createElement from '../../assets/lib/create-element.js';

export default class CartIcon {
  constructor() {
    this.render();

    this.addEventListeners();
  }

  // Отрисовать пустую иконку корзины
  render() {
    this.elem = createElement('<div class="cart-icon"></div>');
  }

  // Заполнить её данными из объекта cart (объяснено ниже)
  update(cart) {
    if (!cart.isEmpty()) {
      this.elem.classList.add('cart-icon_visible');

      this.elem.innerHTML = `
        <div class="cart-icon__inner">
          <span class="cart-icon__count">${cart.getTotalCount()}</span>
          <span class="cart-icon__price">€${cart.getTotalPrice().toFixed(2)}</span>
        </div>`;

      this.updatePosition();

      this.elem.classList.add('shake');
      this.elem.addEventListener('transitionend', () => {
        this.elem.classList.remove('shake');
      }, {once: true});

    } else {
      this.elem.classList.remove('cart-icon_visible');
    }
  }

  addEventListeners() {
    document.addEventListener('scroll', () => this.updatePosition());
    window.addEventListener('resize', () => this.updatePosition());
  }

  // позиционировать иконку корзины на экране
  updatePosition() {
    // ваш код ...
  }
}
```

Использование иконки корзины:

```js
// создаём иконку
let cartIcon = new CartIcon();

// добавляем на страницу
document.body.append(cartIcon.elem);

// обновляем данными из объекта корзины
cartIcon.update(cart);
```

Давайте внимательно посмотрим на метод `update(cart)`.

## Метод update(cart)

Иконка корзины занимается тем, что красиво отображает справа-сверху количество товаров и их общую стоимость.

Сами данные находятся в объекте корзины `cart`.

У него есть три метода:

```js
let cart = {
  isEmpty() {
    // возвращает true если корзина пуста и false если нет
  },

  getTotalCount() {
    // Возвращает общее количество товаров в корзине
  },

  getTotalPrice() {
    // Возвращает общую сумму всех товаров в корзине
  }
}
```

Если в корзине меняется количество товаров, внешний код сначала обновляет их количество в объекте `cart` таким образом, что методы `isEmpty`, `getTotalCount`, `getTotalPrice` начинают возвращать новые значения. После чего вызывает метод `cartIcon.update(cart)`, который вносит изменения в отображение иконки корзины:

Реальный объект `cart` мы сделаем чуть позже, в файле `index.html` вы можете увидеть упрощённый пример такого объекта корзины.

Как видно из примера в файле `index.html`, базовое отображение корзины уже работает. Но если мы прокрутим страницу вниз, то увидим, что иконка корзины рано или поздно останется наверху и станет недоступной. Хотелось бы, чтобы пользователи видели иконку корзины всегда, независимо от того, как сильно они прокрутили страницу. Это вам и предстоит сделать.

## Метод updatePosition()

Реализуйте метод `updatePosition()`, который будет перемещать иконку корзины вниз и, таким образом, делать её видимой, когда пользователь прокручивает страницу.

В коде `CartIcon`, который уже есть, этот метод уже вызывается в методе `update` и в обработчиках событий `scroll` и `resize` (они добавляются в методе `addEventListeners`).

Так что вам не нужно вызывать его, нужно написать его код.

### Требования к перемещению:

Иконка корзины видима только если в корзине есть товары. Изначально она находится сверху-справа страницы.

Перед тем, как начать перемещение, нужно обязательно убедиться, что иконка корзины **видима** на странице. Это можно сделать, например, проверив метрики `offsetHeight` или `offsetWidth` корневого элемента. Подробнее об этом можно прочитать в разделе - [Размеры и прокрутка элементов: offsetWidth/Height](https://learn.javascript.ru/size-and-scroll#offsetwidth-height)

Начинать перемещение иконки корзины нужно сразу, как только при прокрутке верхний край иконки ушёл за пределы экрана.

**По вертикали:**
- иконка корзины должна отстоять на `50px` от верхнего края экрана.

**По горизонтали иконка должна:**
- отстоять на `20px` справа от первого элемент в документе с классом `container`.
- при этом она должна быть не ближе `10px` от правого края окна.

Если ширина окна браузера меньше или равна `767px`, то **перемещать иконку корзины не нужно вовсе**. Это связанно с тем, что на такой ширине у нас используются другие "мобильные" стили. Вы сами можете в этом убедиться в консоли разработчика.

Увидить поведение иконки в действии вы можете на странице проекта <https://course-jsbasic.javascript.ru>.

Обращаем ваше внимание, что в связи с тем, что это задание может быть решено многими способами, его достаточно сложно проверить автоматически. Поэтому если вы сталкнетесь с ситуацией, что в браузере всё работает как надо, но тесты не проходят, обращайтесь к преподавателю за помощью.

### Подсказка

Далее приведено описание того, как можно решать эту задачу. Если вы хотите попробовать решить её сами, не читайте дальше ;)

### Как технически устроено перемещение иконки?

Основная идея перемещение иконки корзины на странице в том, что если пользователь докрутил страницу до верхнего край иконки корзины, задавать ей фиксированное позиционирование (`position: fixed;`). Подробнее про `position: fixed` - [вот тут](https://learn.javascript.ru/position#position-fixed).

### Отслеживание момента начала перемещения

Изначально иконка позиционирована при помощи `position: absolute`. Менять позиционирование на `fixed` и двигать нужно только, когда край иконки зашёл за границу окна. А когда страница обратно прокручена вверх, то нужно вернуть корзину "как было".

Для этого запомним начальную координату верхней границы корзины. Она будет точкой отсчёта: когда страница прокручена ниже - будет плавающая корзина с `fixed`, а когда выше - обычное позиционирование `absolute`:

```js
// текущая Y-координата относительно окна + текущая прокрутка
let initialTopCoord = this.elem.getBoundingClientRect().top + window.pageYOffset;
```

Это значение не обязательно вычислять каждый раз, достаточно рассчитать при первом вызове метода `updatePosition()`.

Полученное значение мы будем сравнивать со степенью прокрутки страницы при каждом новом вызове метода:

```js
if (window.pageYOffset > initialTopCoord) {
  // плавающая корзина
} else {
  // корзина сверху
}
```

Если значение `window.pageYOffset` будет больше `initialTopCoordinate`, значит пользователь уже докрутил до верхнего края иконки корзины, и нам нужно поменять её позиционирование на фиксированное, и наоборот.

### Вычисление отступа по горизонтали

А чтобы понять какой оступ взять слева, нужно вычислить два значения:

1. Значение, чтобы отступ был `20px` справа от первого элемент в документе с классом `container`. Для этого возьмём расстояние от края документа этого самого первого элемента с классом `container` и прибавим к нему `20px`:

```js
document.querySelector('.container').getBoundingClientRect().right + 20;
```

2. Значение, чтобы отступ от правого края экрана был `10px`. Для этого нужно из общей ширины страницы (`document.documentElement.clientWidth`) вычесть ширину иконки корзины(`this.elem.offsetWidth`) и размер отступа от края(`10px`), вот так:

```js
document.documentElement.clientWidth - this.elem.offsetWidth - 10;
```

Теперь нужно выбрать одно значение из двух. Правильным значением для нас будет наименьшее из них. Сделаем это с помощью `Math.min`:
```js
let leftIndent = Math.min(
  document.querySelector('.container').getBoundingClientRect().right + 20,
  document.documentElement.clientWidth - this.elem.offsetWidth - 10
) + 'px'
```

### Применение фиксированного позиционирования

В итоге, чтобы фиксированно спозиционировать иконку корзины на странице нужно:

```js
let leftIndent = Math.min(
  document.querySelector('.container').getBoundingClientRect().right + 20,
  document.documentElement.clientWidth - this.elem.offsetWidth - 10
) + 'px'

Object.assign(this.elem.style, {
  position: 'fixed',
  top: '50px',
  zIndex: 1e3,
  right: '10px',
  left: leftIndent
});
```

Не забудьте всё вернуть как было, когда пользователь докрутил обратно до верха:

```js
Object.assign(this.elem.style, {
  position: '',
  top: '',
  left: '',
  zIndex: ''
});
```

### Мобильные устройства

Для мобильных устройств или просто для небольшой ширины окна у нас используются другие стили, где иконка корзины всегда спозиционированна фиксированно. Поэтому если ширина экрана меньше или равна `767px`, то позиционирование нужно обнулить к исходным значениям. Чтобы это определить используем свойство `clientWidth` всего документа:

```js
let isMobile = document.documentElement.clientWidth <= 767;

// Если условие выполняется, обнуляем стили к исходным
if (document.documentElement.clientWidth <= 767) {
  Object.assign(this.elem.style, {
    position: '',
    top: '',
    left: '',
    zIndex: ''
  });
}
```
